
local wx = require "wx"
local rcr = require "barf.rcr"
local _G = _G
local ipairs,pairs,error
    = ipairs,pairs,error
local strbyte, strsub, strmatch, strrep, strchar
    = string.byte, string.sub, string.match, string.rep, string.char
local tconcat = table.concat
local bit = require "bit"


module "barf.editors"


local function extract_uint16(bank,i)
    local b1,b2 = strbyte(bank,i+1,i+2);
    return b1 + (b2 * 0x100);
end

local function extract_5terminated_string(bank,pos)
    return rcr.decodestring(strmatch(bank,"[^\005]*",pos+1));
end

function text(info, parentWindow)
    -- The ROM image being edited. See barf.nes.readrom()
    local rom = _G.rom;
    -- The place to put the editor's controls
    --local parentWindow = _G.mainWindow;
    
    -- The bank that holds the string data
    --local bank = rom.prg[info.bank];
    
    -- Extract the strings to edit
    local strings = {};
    for ptrptr = info.start, info.start + (2*info.count), 2 do
        local ptr = rom:uint16_le(info.rom, info.bank, ptrptr);
        -- ptr_OR is a mask of bits that get set on an address in the bank
        -- when it is stored in the ROM
        if (ptr == 0) then
            strings[#strings+1] = "";
        else
            ptr = bit.band(ptr, bit.bnot(info.ptr_OR));
            strings[#strings+1] = rom:string(info.rom, info.bank, ptr); --extract_5terminated_string(bank, ptr);
        end
    end
    
    local function writeback()
        local count = info.count;
        local strpos = info.start + (count*2) + 2;
        rom:bytestring(info.rom, info.bank, info.start + info.length - 1, '\005');
        local copies = {[""] = info.start + info.length - 1};
        for i,s in ipairs(strings) do
            local pos = copies[s];
            if pos then
                rom:uint16_le(info.rom, info.bank, info.start + (i-1)*2, pos);
            else
                pos = info.ptr_OR + strpos;
                copies[s] = pos;
                rom:uint16_le(info.rom, info.bank, info.start + (i-1)*2, info.ptr_OR + strpos);
                strpos = rom:string(info.rom, info.bank, strpos, s);
            end
        end
    end
    
    local box = wx.wxBoxSizer(wx.wxVERTICAL)
    
        -- List box of strings
        local stringsBox = wx.wxListBox(
            parentWindow,
            wx.wxID_ANY,
            wx.wxDefaultPosition,
            wx.wxDefaultSize,
            strings,
            wx.wxLB_SINGLE + wx.wxLB_ALWAYS_SB,
            wx.wxDefaultValidator,
            "listBox");
        box:Add(stringsBox, 1, wx.wxEXPAND + wx.wxALL, 0);
        
        local edit_sizer = wx.wxBoxSizer(wx.wxHORIZONTAL)
        
            -- Individual string editor
            local editStringBox = wx.wxTextCtrl(
                parentWindow,
                wx.wxID_ANY,
                "",
                wx.wxDefaultPosition,
                wx.wxDefaultSize,
                wx.wxTE_PROCESS_ENTER,
                rcr.textvalidator);
            edit_sizer:Add(editStringBox, 1, wx.wxALL, 0);
            
            -- "Update" button
            local updateButton = wx.wxButton(
                parentWindow,
                wx.wxID_ANY,
                "Update",
                wx.wxDefaultPosition,
                wx.wxDefaultSize);
            edit_sizer:Add(updateButton, 0, wx.wxALL, 0);
        
        box:Add(edit_sizer, 0, wx.wxEXPAND + wx.wxALL, 0);
    
    parentWindow.Sizer = box;
    parentWindow:Layout();
    
    local editIndex = nil;
    editStringBox:Enable(false);
    updateButton:Enable(false);
    
    stringsBox:Connect(wx.wxID_ANY, wx.wxEVT_COMMAND_LISTBOX_SELECTED,
        function(event)
            editIndex = event.Selection + 1;
            editStringBox.Value = strings[editIndex];
            editStringBox:Enable(true);
            updateButton:Enable(false);
        end);
    
    editStringBox:Connect(wx.wxID_ANY, wx.wxEVT_COMMAND_TEXT_UPDATED,
        function()
            -- "Update" button only enabled when the value has been modified
            if editStringBox:IsModified() then
                updateButton:Enable(true);
            end
        end);
    
    editStringBox:Connect(wx.wxID_ANY, wx.wxEVT_COMMAND_TEXT_ENTER,
        function()
            -- simulate a click on updateButton
            updateButton:ProcessEvent(
                wx.wxCommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, updateButton.Id)
            );
        end);
    
    updateButton:Connect(wx.wxID_ANY, wx.wxEVT_COMMAND_BUTTON_CLICKED,
        function()
            if (editIndex == nil) then
                -- shouldn't happen
                updateButton:Enable(false);
                return;
            end
            local oldvalue = strings[editIndex];
            strings[editIndex] = editStringBox.Value;
            local success,message = pcall(writeback)
            if not success then
                editStringBox.Value = oldvalue;
                strings[editIndex] = oldvalue;
                wx.wxMessageBox(message);
            else
                stringsBox:SetString(editIndex-1, editStringBox.Value);
            end
            stringsBox:SetFocus();
            updateButton:Enable(false);
        end);
end

return text;
